package process.service.impl;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import javax.annotation.PostConstruct;

import org.apache.commons.lang.StringUtils;
import org.jsoup.nodes.Document;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import process.model.AnnotatedPage;
import process.model.DetailP;
import process.model.MenuNode;
import process.model.OntoResource;
import process.model.ProcessResult;
import process.model.event.AnnotatedPageEvent;
import process.model.lookup.OntoLookUp;
import process.model.lookup.ProductLookup;
import process.module.DetailParser;
import process.module.OntoQuery;
import process.module.OntologyBuilder;
import process.module.TextProcess;
import process.service.AnnieProcessService;
import process.service.OntoQueryService;

import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;

import core.constant.SystemConstant;
import domain.web.dao.CategoryDao;
import domain.web.dao.DetailDao;
import domain.web.dao.ManufacturerDao;
import domain.web.dao.ProductDao;
import domain.web.model.Detail;
import domain.web.model.Product;

@Service
public class OntoQueryServiceImpl implements OntoQueryService {

	@Autowired
	private EventBus eventBus;
	@Autowired
	private DetailDao detailDao;
	@Autowired
	private ProductDao productDao;
	@Autowired
	private CategoryDao categoryDao;
	@Autowired
	private ManufacturerDao manufacturerDao;
	@Autowired
	private AnnieProcessService annieProcessService;

	private OntoQuery ontoQuery;
	private DetailParser detailParser;
	private List<ProductLookup> unDefinedNews;
	private List<ProductLookup> definedNews;
	private List<ProductLookup> updates;
	private List<OntoResource> allProducts;
	private List<DetailP> unDefinedNewsDetail;
	private OntologyBuilder ontologyBuilder;

	// private List<ProductLookup> updates = new ArrayList<ProductLookup>();

	@PostConstruct
	public void initialize() throws Exception {
		eventBus.register(this);
		ontoQuery = new OntoQuery();
		detailParser = new DetailParser();
		allProducts = ontoQuery.getAllInstance();
		ontologyBuilder = new OntologyBuilder(SystemConstant.O_FILE);

		unDefinedNews = new ArrayList<ProductLookup>();
		definedNews = new ArrayList<ProductLookup>();
		updates = new ArrayList<ProductLookup>();
		unDefinedNewsDetail = new ArrayList<DetailP>();
	}

	@Override
	@Subscribe
	public void handleAnnotatedPageEvent(AnnotatedPageEvent event) {
		OntoResource menu = null;
		MenuNode node = event.getMenuNode();
		// Find suitable menu
		menu = menuLookUp(node);

		List<OntoResource> products = new ArrayList<OntoResource>();
		if (menu == null) {
			// If no menu found, get all instances
			products = allProducts;
		} else {
			// If menu found, Get instances by found class
			products = ontoQuery.getInstanceFromClass(menu.getName());
		}

		// Find out approximate instance in Ontology for each product
		for (AnnotatedPage page : event.getAnnotatedPages()) {
			for (ProductLookup lookup : page.getProductLookups()) {
				// clear name and URI
				lookup.setName(TextProcess.clear(lookup.getName()));
				lookup.setUri(TextProcess.clear(lookup.getUri()));

				OntoResource product = productLookUp(lookup.getName(), products);
				if (product == null) {
					lookup.setUri(lookup.getName().trim().replaceAll(" ", "_"));
					// if menu found, then add Onto Class to the new product
					if (menu != null) {
						lookup.setClassName(menu.getName());
					}
					unDefinedNews.add(lookup);
					DetailP detailP = new DetailP();
					try {
						detailP = detailParser.parse(page.getDocument(),
								lookup.getName());
					} catch (Exception ex) {

					}
					unDefinedNewsDetail.add(detailP);

					System.out.println("*** Product Unknow: "
							+ lookup.getName() + " / Class:"
							+ lookup.getClassName());
				} else {

					// get class name of product
					OntoResource resource = ontoQuery.getClassFromInstance(
							OntoQuery.getNameFromUri(product.getUri())).get(0);
					lookup.setClassName(resource.getName());

					// Compare accurately the lookup and the found product
					if (TextProcess.compareTextOnly(lookup.getName(),
							product.getName())) {
						// If equal absolutely => UPDATE
						lookup.setUri(OntoQuery.getNameFromUri(product.getUri()));
						// UPDATE DATABASEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
						updateProduct(lookup, page.getDocument(),
								event.getStoreId());
						updates.add(lookup);

					} else { // If not equal absolutely => NEW Instance
						lookup.setUri(lookup.getName().trim()
								.replaceAll(" ", "_"));
						// check if it is in definedNews
						if (checkExisted(lookup.getUri())) {
							continue;
						}

						try {
							lookup.setManufacturer(annieProcessService
									.getProduct(lookup.getName()).trim()
									.replaceAll(" ", "_"));
						} catch (Exception ex) {
							System.out.println(ex);
						}
						// news.add(lookup);

						try {
							// INSERT INTO ONTOLOGYYYYYYYYYYYYYYYYYYYYYYYYYY
							//****************ontologyBuilder.addIndividual(lookup.getUri(),lookup.getClassName(),lookup.getManufacturer());
							// INSERT INTO DATABASEEEEEEEEEEEEEEEEEEEEEEE
							//****************insertProduct(lookup, page.getDocument());
							//****************updateProduct(lookup, page.getDocument(),event.getStoreId());
							definedNews.add(lookup);
						} catch (Exception ex) {
							System.out.println(ex.getMessage());
						}
					}
				}
			}
			System.out
					.println("----------------------------------------------------");
		}
		ontoQuery.reset();
	}

	// Get Class approximately to Menu Node
	private OntoResource menuLookUp(MenuNode node) {
		OntoResource menu = null;
		// loop to Menu and it's parent to find suitable class
		while (node != null) {
			String nodeText = node.getText().replaceAll(" ", "_");
			List<OntoLookUp> lookupMenus = new ArrayList<OntoLookUp>();
			List<OntoResource> resources = ontoQuery.getAllClasses();
			// find approximate Menu
			for (OntoResource resource : resources) {
				String text = resource.getName();
				double distance = (double) StringUtils.getLevenshteinDistance(
						nodeText, text) / text.length();
				if (distance <= SystemConstant.MENU_THRESHOLD) {
					lookupMenus.add(new OntoLookUp(resource, distance));
				}
			}

			if (!lookupMenus.isEmpty()) {
				Collections.sort(lookupMenus);
				menu = lookupMenus.get(0).getResource();
				break;
			}
			// retrive the parent of menu node to find suitable menu
			node = node.getParent();
		}
		return menu;
	}

	// Get Instance approximately to Product
	private OntoResource productLookUp(String productText,
			List<OntoResource> products) {
		productText = TextProcess.clear(productText);
		productText = productText.replaceAll(" ", "_");
		productText = productText.toUpperCase();
		List<OntoLookUp> lookups = new ArrayList<OntoLookUp>();
		// find approximate Product
		for (OntoResource resource : products) {
			String text = resource.getName().toUpperCase();
			double distance = (double) StringUtils.getLevenshteinDistance(
					productText, text) / text.length();
			if (distance <= SystemConstant.PRODUCT_THRESHOLD) {
				lookups.add(new OntoLookUp(resource, distance));
			}
		}

		if (lookups.isEmpty()) {
			return null;
		} else {
			Collections.sort(lookups);
			return lookups.get(0).getResource();
		}
	}

	private void updateProduct(ProductLookup lookup, Document document,
			long storeId) {
		System.out.println("Select URI with: " + lookup.getUri());
		Product product = productDao.get(lookup.getUri());
		if (product == null) {
			return;
		}
		System.out.println("--- FOUND PRODUCT: Name=" + lookup.getName()
				+ " Id=" + product.getId() + " ---");
		lookup.setId(product.getId());

		Detail detail = detailDao.get(product.getId(), storeId);
		// Check if product and Store existed in table Details

		if (detail == null) {
			// If not existed => Insert new
			try {
				// Try to get the detail
				DetailP detailP = new DetailP();
				try {
					detailP = detailParser.parse(document, lookup.getName());
					System.out.println("\tGotten Detail");
				} catch (Exception ex) {
					ex.printStackTrace();
				}

				detail = new Detail(product.getId(), storeId,
						lookup.getPrice(), detailP.getDetailUrl(),
						detailP.getImageUrl());
				// insert into DB
				System.out
						.println("\t==> DETAIL Inserted with PId:"
								+ detail.getProductId() + " / StoreId:"
								+ detail.getStoreId() + " / Price:"
								+ detail.getPrice());
				detailDao.insert(detail);

				// update Product images
				if (product.getImageUrl().isEmpty()) {
					product.setImageUrl(detailP.getImageUrl());
					productDao.update(product);
				}
			} catch (Exception ex) {
				System.out.println(ex.getMessage());
			}
		} else {
			// If existed => Update
			detail.setPrice(lookup.getPrice());
			// Update to DB
			System.out.println("\t==> DETAIL Updated with PId:"
					+ detail.getProductId() + " / StoreId:"
					+ detail.getStoreId() + " / Price:" + detail.getPrice());
			detailDao.update(detail);

			// update Product images
			// product.setImageUrl(detailP.getImageUrl());
			// productDao.update(product);
		}
	}

	private void insertProduct(ProductLookup lookup, Document document) {
		long catId = categoryDao.get(lookup.getClassName(), "vi").getId();
		// long manuId = manufacturerDao.get(
		// OntoQuery.getClearName(lookup.getManufacturer())).getId();
		long manuId = manufacturerDao.getByUri(
				lookup.getManufacturer().replace(" ", "_")).getId();

		// get Image
		DetailP detailP = new DetailP();
		try {
			detailP = detailParser.parse(document, lookup.getName());
			System.out.println("\tGotten Detail");
		} catch (Exception ex) {
			ex.printStackTrace();
		}

		Product product = new Product();
		product.setName(lookup.getName());
		product.setUri(lookup.getUri());
		product.setCategoryId(catId);
		product.setManufacturerId(manuId);
		product.setImageUrl(detailP.getImageUrl());
		long id = productDao.insert(product);
		lookup.setId(id);
		System.out.println("--- NEW PRODUCT:" + product.getName() + "  PId:"
				+ id + " ---");
	}

	@Override
	public ProcessResult getResult() {
		ProcessResult processResult = new ProcessResult(definedNews,
				unDefinedNews, updates);
		processResult.setUnDefinedNewsDetail(unDefinedNewsDetail);
		return processResult;
	}

	public void setUnDefinedNews(List<ProductLookup> unDefinedNews) {
		this.unDefinedNews = unDefinedNews;
	}

	public void setDefinedNews(List<ProductLookup> definedNews) {
		this.definedNews = definedNews;
	}

	public void setUpdates(List<ProductLookup> updates) {
		this.updates = updates;
	}

	private boolean checkExisted(String uri) {
		for (ProductLookup lookup : definedNews) {
			if (lookup.getUri().equalsIgnoreCase(uri)) {
				return true;
			}
		}
		return false;
	}
}